{-# LANGUAGE ScopedTypeVariables #-}
module Network.PGI
  (
    serve
  , Route
  , Handler
  ) where

import Prelude hiding (catch)

import TNET

import Control.Applicative ((<$>))
import Control.Exception (catch, SomeException)
import Control.Monad (forever)
import Data.Attoparsec.Enumerator
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import Data.Enumerator (($$), run)
import Data.Enumerator.Binary (enumHandle)
import Data.Maybe (isJust)
import System.Exit
import System.IO

data PGIRequest = PGIRequest {
  pRequest   :: Maybe TValue,
  pRequestID :: Maybe String,
  pCommand   :: Maybe String,
  pRoute     :: Maybe String
  }
  deriving (Eq, Show)

instance TNET PGIRequest where
  toTNET _ = undefined
  fromTNET tval = let
    request   = tval .: "request"
    requestID = B8.unpack <$> tval .: "request_id"
    command   = B8.unpack <$> tval .: "command"
    route     = tval .: "route"
    in Just $ PGIRequest request requestID command route

type Route = String
type Handler = TValue -> IO TValue

serve :: [(Route, Handler)] -> IO ()
serve handlers = do
  hSetBuffering stdin NoBuffering
  serveLoop handlers

serveLoop :: [(Route, Handler)] -> IO ()
serveLoop handlers = forever $ do
  raw_request <- run (enumHandle 1 stdin $$ iterParser tnetParser)
  case raw_request of
    Left e -> exitSuccess
    Right raw -> do let request = fromTNET raw :: Maybe PGIRequest
                    case request of
                      Nothing -> errorTNET "Could not parse request"
                      Just req  ->
                        case pCommand req of
                          Just command -> handleCommand command
                          Nothing      -> if validRequest req
                                          then handleRequest handlers req
                                          else errorTNET "Invalid Request"

handleCommand :: String -> IO ()
handleCommand "init" = outputTNET okResponse
  where okResponse = dict [ "result" .= "ok" ]
handleCommand c      = errorTNET $ "Unsupported command: " ++ c

outputTNET :: TValue -> IO ()
outputTNET t = B.putStr (encode t) >> hFlush stdout

errorTNET :: String -> IO ()
errorTNET e = outputTNET errorMsg
  where errorMsg = dict ["dev_error" .= dict [ "message" .= e, "trace" .= "" ]]

validRequest :: PGIRequest -> Bool
validRequest req = and requiredFields
  where requiredFields = [isJust $ pRequest req, isJust $ pRoute req]

handleRequest :: [(Route, Handler)] -> PGIRequest -> IO ()
handleRequest handlers request =
  case lookup route handlers of
    Nothing -> errorTNET $ "Route not found: " ++ route
    Just handler ->
      catch (handler appRequest >>= outputTNETResponse)
            (\(e :: SomeException) -> errorTNET $ show e)
  where
    Just route = pRoute request
    Just appRequest = pRequest request

    outputTNETResponse r = outputTNET $
      dict [ "response" .= r ]
